package com.tfswx.jcyw.dmzc.util;

import com.intellij.codeInsight.hint.EditorFragmentComponent;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.util.TextRange;
import com.intellij.ui.JBColor;
import com.intellij.ui.scale.JBUIScale;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.regex.Pattern;

@Slf4j
public final class ImageBuilder {
    private static final Pattern EMPTY_SUFFIX = Pattern.compile("\n\\s+$");
    private static final boolean myRemoveCaret = true;
    private final double myScale;
    private final int myPadding;
    private final boolean myChopIndentation = false;
    private final Editor editor;

    public ImageBuilder(Editor editor) {
        super();
        this.editor = editor;
        this.myScale = 1.0;
        this.myPadding = 10;
    }

    public static void paint(Graphics2D g, JComponent contentComponent, AffineTransform at, int width, int height, Color backgroundColor, int padding) {
        double scale = JBUIScale.sysScale(contentComponent);
        int scaledWidth = (int) ((double) width * scale);
        int scaledHeight = (int) ((double) height * scale);
        int imgWidth = scaledWidth + 2 * padding;
        int imgHeight = scaledHeight + 2 * padding;
        g.setColor(backgroundColor);
        g.fillRect(0, 0, imgWidth, imgHeight);
        g.translate(padding, padding);
        g.clipRect(0, 0, scaledWidth, scaledHeight);
        g.transform(at);
        contentComponent.paint(g);
    }

    public static void includePoint(Rectangle2D r, Point2D p) {
        if (r.isEmpty()) {
            r.setFrame(p, new Dimension(1, 1));
        } else {
            r.add(p);
        }

    }

    public static TextRange getRange(Editor editor) {
        SelectionModel selectionModel = editor.getSelectionModel();
        int start = selectionModel.getSelectionStart();
        int end = selectionModel.getSelectionEnd();
        return new TextRange(start, end);
    }

    public byte[] createImage() {
        TextRange range = getRange(this.editor);
        Document document = this.editor.getDocument();
        EditorState state = EditorState.from(this.editor);

        byte[] bytes;
        try {
            this.resetEditor();
            double scale = this.myScale;
            JComponent contentComponent = this.editor.getContentComponent();
            Graphics2D contentGraphics = (Graphics2D) contentComponent.getGraphics();

            AffineTransform currentTransform = contentGraphics.getTransform();
            AffineTransform newTransform = new AffineTransform(currentTransform);
            newTransform.scale(scale, scale);
            JBColor color = JBColor.BLACK;
            this.paint(contentComponent, newTransform, 1, 1, color, 0);
            String text = document.getText(range);
            Rectangle2D r = this.getSelectionRectangle(range, text);
            newTransform.translate(-r.getX(), -r.getY());
            int w = (int) (r.getWidth() * scale);
            int h = (int) (r.getHeight() * scale);
            Color backgroundColor = EditorFragmentComponent.getBackgroundColor(this.editor, false);
            BufferedImage paint = this.paint(contentComponent, newTransform, w, h, backgroundColor, this.myPadding);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(paint, "png", os);
            bytes = os.toByteArray();
        } catch (IOException e) {
            log.error("生成图片失败", e);
            bytes = null;
        } finally {
            state.restore(this.editor);
        }

        return bytes;
    }

    private BufferedImage paint(JComponent contentComponent, AffineTransform at, int width, int height, Color backgroundColor, int padding) {
        double scale = JBUIScale.sysScale(contentComponent);
        BufferedImage img = new BufferedImage((int) ((double) width * scale + (double) (2 * padding)), (int) ((double) height * scale + (double) (2 * padding)), 1);
        Graphics graphics = img.getGraphics();
        if (graphics == null) {
            throw new NullPointerException("null cannot be cast to non-null type java.awt.Graphics2D");
        } else {
            Graphics2D g = (Graphics2D) graphics;
            paint(g, contentComponent, at, width, height, backgroundColor, padding);
            return img;
        }
    }

    public long getSelectedSize() {
        TextRange range = getRange(this.editor);
        Document document = this.editor.getDocument();
        String text = document.getText(range);
        return text.length();
    }

    private void resetEditor() {
        Document document = this.editor.getDocument();
        TextRange range = getRange(this.editor);
        this.editor.getSelectionModel().setSelection(0, 0);
        this.editor.getCaretModel().moveToOffset(range.getStartOffset() == 0 ? document.getLineEndOffset(document.getLineCount() - 1) : 0);
        if (this.editor instanceof EditorEx) {
            ((EditorEx) this.editor).setCaretEnabled(false);
        }

        EditorSettings settings = this.editor.getSettings();
        settings.setCaretRowShown(false);
    }

    private Rectangle2D getSelectionRectangle() {
        TextRange range = getRange(this.editor);
        Document document = this.editor.getDocument();
        String text = document.getText(range);
        return this.getSelectionRectangle(range, text);
    }

    private Rectangle2D getSelectionRectangle(TextRange range, String text) {
        int start = range.getStartOffset();
        int end = range.getEndOffset();
        Rectangle2D r = new Rectangle2D.Double();
        int i = start;
        if (start <= end) {
            while (true) {
                label28:
                {
                    if (this.myChopIndentation) {
                        int min = Math.min(i - start + 1, text.length());
                        String s = text.substring(0, min);
                        if (EMPTY_SUFFIX.matcher(s).find()) {
                            break label28;
                        }
                    }

                    VisualPosition pos = this.editor.offsetToVisualPosition(i);
                    Point2D point = this.editor.visualPositionToXY(pos);
                    includePoint(r, point);
                    includePoint(r, new Point2D.Double(point.getX(), point.getY() + (double) this.editor.getLineHeight()));
                }

                if (i == end) {
                    break;
                }

                ++i;
            }
        }

        for (Inlay<?> value : this.editor.getInlayModel().getInlineElementsInRange(start, end)) {
            Rectangle bounds = value.getBounds();
            if (bounds != null) {
                r.add(bounds);
            }
        }

        return r;
    }

    public int getStartLineNum() {
        Document document = this.editor.getDocument();
        return document.getLineNumber(editor.getSelectionModel().getSelectionStart()) + 1;
    }

    public int getEndLineNum() {
        Document document = this.editor.getDocument();
        return document.getLineNumber(editor.getSelectionModel().getSelectionEnd()) + 1;
    }

    public static final class EditorState {
        private final TextRange range;
        private final int offset;
        private final boolean caretRow;

        public EditorState(TextRange range, int offset, boolean caretRow) {
            super();
            this.range = range;
            this.offset = offset;
            this.caretRow = caretRow;
        }

        public static EditorState from(Editor editor) {
            TextRange range = getRange(editor);
            CaretModel caretModel = editor.getCaretModel();
            int offset = caretModel.getOffset();
            EditorSettings settings = editor.getSettings();
            return new EditorState(range, offset, settings.isCaretRowShown());
        }

        public void restore(Editor editor) {
            EditorSettings settings = editor.getSettings();
            settings.setCaretRowShown(this.caretRow);
            SelectionModel selectionModel = editor.getSelectionModel();
            CaretModel caretModel = editor.getCaretModel();
            if (editor instanceof EditorEx) {
                ((EditorEx) editor).setCaretEnabled(true);
            }

            caretModel.moveToOffset(this.offset);
            selectionModel.setSelection(this.range.getStartOffset(), this.range.getEndOffset());
        }
    }

}
